﻿///////////////////////////////////////////////////////////////////////////////
//
// This file is part of the BassDrop project
//   https://code.google.com/p/bass-drop/
//
//
// Creator:	Gergely Krajcsovszki (krajcsovszkig@hotmail.com)
// Date:	2013-08-20 14:00
//
//
// Copyright © 2013-2014. Gergely Krajcsovszki
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
///////////////////////////////////////////////////////////////////////////////
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using BassDrop.Core.Common.Data.IO.Portable;
using BassDrop.Core.Common.Utilities;

namespace BassDrop.Core.Desktop.Data.IO.Desktop
{
	/// <summary>
	/// Desktop implementation of file handling methods
	/// </summary>
	public class FileHandler : IFileHandler
	{
		#region IFileHandler Members

		/// <summary>
		/// Creates a uniquely named, zero-byte temporary file on disk and returns the full path of that file.
		/// </summary>
		/// <returns>The full path of the temporary file.</returns>
		/// <see cref="System.IO.Path.GetTempFileName()"/>
		public string GetTempFileName()
		{
			return Path.GetTempFileName();
		}

		/// <summary>
		/// Opens a text file, reads all lines of the file into a string, and then closes the file.
		/// </summary>
		/// <param name="path">The file to open for reading.</param>
		/// <returns>A string containing all lines of the file.</returns>
		/// <see cref="System.IO.File.ReadAllText(string)"/>
		public string ReadAllText( string path )
		{
			return File.ReadAllText( path );
		}

		/// <summary>
		/// Creates a new file, write the contents to the file, and then closes the file. If the target file already exists, it is overwritten.
		/// </summary>
		/// <param name="path">The file to write to.</param>
		/// <param name="contents">The string to write to the file.</param>
		public void WriteAllText( string path, string contents )
		{
			FileInfo fileInfo = new FileInfo( path );

			if ( !fileInfo.Directory.Exists )
			{
				fileInfo.Directory.Create();
			}

			this.UnMarkDeleted( path );

			File.WriteAllText( path, contents );
		}

		/// <summary>
		/// Deletes the specified file.
		/// </summary>
		/// <param name="path">The name of the file to be deleted. Wildcard characters are not supported.</param>
		/// <see cref="System.IO.File.Delete(string)"/>
		public void Delete( string path )
		{
			File.Delete( path );
		}

		/// <summary>
		/// Returns the directory information for the specified path string.
		/// </summary>
		/// <param name="path">The path of a file or directory.</param>
		/// <returns>Directory information for path, or null if path denotes a root directory or is null. Returns String.Empty if path does not contain directory information.</returns>
		/// <see cref="System.IO.Path.GetDirectoryName(string)"/>
		public string GetDirectoryName( string path )
		{
			return Path.GetDirectoryName( path );
		}

		/// <summary>
		/// Returns the file name and extension of the specified path string.
		/// </summary>
		/// <param name="path">The path string from which to obtain the file name and extension.</param>
		/// <returns>
		/// The characters after the last directory character in path.
		/// If the last character of path is a directory or volume separator character, this method returns String.Empty.
		/// If path is null, this method returns null.
		/// </returns>
		/// <see cref="System.IO.Path.GetFileName(string)"/>
		public string GetFileName( string path )
		{
			return Path.GetFileName( path );
		}

		/// <summary>
		/// Returns the extension of the specified path string.
		/// </summary>
		/// <param name="path">The path string from which to get the extension.</param>
		/// <returns>
		/// The extension of the specified path (including the period "."), or null, or String.Empty.
		/// If path is null, GetExtension returns null. If path does not have extension information,
		/// GetExtension returns String.Empty.
		/// </returns>
		/// <see cref="System.IO.Path.GetExtension(string)"/>
		public string GetExtension( string path )
		{
			return Path.GetExtension( path );
		}

		/// <summary>
		/// Determines whether the specified file exists.
		/// </summary>
		/// <param name="path">The file to check.</param>
		/// <returns>
		/// true if the caller has the required permissions and path contains the name of an existing file;
		/// otherwise, false.
		/// This method also returns false if path is null, an invalid path, or a zero-length string.
		/// If the caller does not have sufficient permissions to read the specified file, no exception is
		/// thrown and the method returns false regardless of the existence of path.
		/// </returns>
		/// <see cref="System.IO.File.Exists(string)"/>
		public bool Exists( string path )
		{
			return File.Exists( path );
		}

		/// <summary>
		/// Moves a specified file to a new location, providing the option to specify a new file name.
		/// </summary>
		/// <param name="sourceFileName">The name of the file to move.</param>
		/// <param name="destFileName">The new path for the file.</param>
		/// <see cref="System.IO.File.Move(string, string)"/>
		public void Move( string sourceFileName, string destFileName )
		{
			File.Move( sourceFileName, destFileName );
		}

		/// <summary>
		/// Returns the list of files from a directory
		/// </summary>
		/// <param name="directoryPath"></param>
		/// <returns></returns>
		public IEnumerable<string> GetFiles( string directoryPath )
		{
			if ( Directory.Exists( directoryPath ) )
			{
				return Directory.GetFiles( directoryPath );
			}
			else
			{
				return Enumerable.Empty<string>();
			}
		}

		/// <summary>
		/// Returns the list of subdirectories from a directory
		/// </summary>
		/// <param name="directoryPath"></param>
		/// <returns></returns>
		public IEnumerable<string> GetDirectories( string directoryPath )
		{
			if ( Directory.Exists( directoryPath ) )
			{
				return Directory.GetDirectories( directoryPath );
			}
			else
			{
				return Enumerable.Empty<string>();
			}
		}

		/// <summary>
		/// Returns the list of files and subdirectories from a directory
		/// </summary>
		/// <param name="directoryPath"></param>
		/// <returns></returns>
		public IEnumerable<string> GetFileSystemEntries( string directoryPath )
		{
			return Directory.GetFileSystemEntries( directoryPath );
		}

		/// <summary>
		/// Converts the passed relative path string to a Uri
		/// </summary>
		/// <param name="relativePath"></param>
		/// <returns></returns>
		public Uri ToUri( string relativePath )
		{
			return new Uri( Path.GetFullPath( relativePath ) );
		}

		/// <summary>
		/// Opens the file on the passed URI and returns a stream to it.
		/// Similar to System.IO.File.Open
		/// </summary>
		/// <param name="path">The path to the file.</param>
		/// <param name="mode">A FileMode value that specifies whether a file is created if one does not exist, and determines whether the contents of existing files are retained or overwritten.</param>
		/// <param name="access">A FileAccess value that specifies the operations that can be performed on the file.</param>
		/// <param name="share">A FileShare value specifying the type of access other threads have to the file.</param>
		/// <param name="createDirs">Automatically create directories for the file if needed</param>
		/// <returns>a stream to the file</returns>
		public Stream GetStream( string path, PortableFileMode mode, PortableFileAccess access, PortableFileShare share, bool createDirs = true )
		{
			FileMode stdMode;
			FileAccess stdAccess;
			FileShare stdShare;

			switch ( mode )
			{
				case PortableFileMode.Append:
					stdMode = FileMode.Append;
					break;
				case PortableFileMode.Create:
					stdMode = FileMode.Create;
					break;
				case PortableFileMode.CreateNew:
					stdMode = FileMode.CreateNew;
					break;
				case PortableFileMode.Open:
					stdMode = FileMode.Open;
					break;
				case PortableFileMode.OpenOrCreate:
					stdMode = FileMode.OpenOrCreate;
					break;
				case PortableFileMode.Truncate:
					stdMode = FileMode.Truncate;
					break;
				default:
					stdMode = FileMode.Open;
					break;
			}

			switch ( access )
			{
				case PortableFileAccess.Read:
					stdAccess = FileAccess.Read;
					break;
				case PortableFileAccess.Write:
					stdAccess = FileAccess.Write;
					break;
				case PortableFileAccess.ReadWrite:
					stdAccess = FileAccess.ReadWrite;
					break;
				default:
					stdAccess = FileAccess.Read;
					break;
			}

			switch ( share )
			{
				case PortableFileShare.Delete:
					stdShare = FileShare.Delete;
					break;
				case PortableFileShare.Inheritable:
					stdShare = FileShare.Inheritable;
					break;
				case PortableFileShare.None:
					stdShare = FileShare.None;
					break;
				case PortableFileShare.Read:
					stdShare = FileShare.Read;
					break;
				case PortableFileShare.ReadWrite:
					stdShare = FileShare.ReadWrite;
					break;
				case PortableFileShare.Write:
					stdShare = FileShare.Write;
					break;
				default:
					stdShare = FileShare.None;
					break;
			}

			if ( createDirs && (mode != PortableFileMode.Open && mode != PortableFileMode.Truncate) )
			{
				FileInfo fileInfo = new FileInfo( path );

				if ( !fileInfo.Directory.Exists )
				{
					fileInfo.Directory.Create();
				}
			}

			switch ( stdMode )
			{
				case FileMode.Append:
				case FileMode.Create:
				case FileMode.CreateNew:
				case FileMode.OpenOrCreate:
					this.UnMarkDeleted( path );
					break;
			}

			return File.Open( path, stdMode, stdAccess, stdShare );
		}

		/// <summary>
		/// Returns the MD5 hash of the passed stream
		/// </summary>
		/// <param name="path"></param>
		/// <returns></returns>
		/// <remarks> From http://stackoverflow.com/a/2151345/301134 </remarks>
		public string GetMD5Hash( string path )
		{
			using ( var md5 = MD5.Create() )
			{
				return BitConverter.ToString( md5.ComputeHash( File.ReadAllBytes( path ) ) ).Replace( "-", "" );
			}
		}

		/// <summary>
		/// Returns the path of the personal directory of the current user
		/// </summary>
		/// <returns></returns>
		public string GetUserDir()
		{
			return Path.Combine( Environment.GetFolderPath( Environment.SpecialFolder.ApplicationData ), "BassDrop" );
		}

		/// <summary>
		/// Returns the metadata of the passed file
		/// </summary>
		/// <param name="path"></param>
		/// <returns></returns>
		public FileMetadata GetMetadata( string path )
		{
			FileMetadata metadata = new FileMetadata();

			metadata.LastWriteTimeUtc = File.GetLastWriteTimeUtc( path );

			return metadata;
		}

		/// <summary>
		/// Sets the metadata of the passed file
		/// </summary>
		/// <param name="path"></param>
		/// <param name="metadata"></param>
		/// <returns></returns>
		public void SetMetadata( string path, FileMetadata metadata )
		{
			if ( metadata.LastWriteTimeUtc != null )
			{
				File.SetLastWriteTimeUtc( path, (DateTime) metadata.LastWriteTimeUtc );
			}
		}

		#endregion
	}
}
